package com.brightlight.dreamcrafter.common.util;

import cn.hutool.core.io.FastByteArrayOutputStream;
import cn.hutool.core.util.StrUtil;
import com.brightlight.dreamcrafter.common.config.MinioConfig;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import static com.brightlight.dreamcrafter.common.constant.CommonConstant.LINK_FILE_PATH;

/**
 * @description: Minio操作工具类
 * @author: LiMG
 * @create: 2024-03-17 22:33:42
 **/
@Component
@Slf4j
public class MinioUtil {

    /**
     * 注入MinIO的配置信息
     */
    @Autowired
    private MinioConfig prop;

    /**
     * 注入MinIO客户端
     */
    @Resource
    private MinioClient minioClient;

    /**
     * 查看存储bucket是否存在
     *
     * @return boolean
     */
    public Boolean bucketExists(String bucketName) {
        Boolean found;
        try {
            found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return found;
    }

    /**
     * 创建存储bucket
     *
     * @return Boolean
     */
    public Boolean makeBucket(String bucketName) {
        try {
            minioClient.makeBucket(MakeBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 删除存储bucket
     *
     * @return Boolean
     */
    public Boolean removeBucket(String bucketName) {
        try {
            minioClient.removeBucket(RemoveBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 获取全部bucket
     */
    public List<Bucket> getAllBuckets() {
        try {
            List<Bucket> buckets = minioClient.listBuckets();
            return buckets;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取单文件的对象名
     *
     * @param originName
     * @return
     */
    private String getSingleObjectName(String originName) {
        String fileName = IDUtil.id() + originName.substring(originName.lastIndexOf("."));
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        return sdf.format(new Date()) + "/" + fileName;
    }

    /**
     * 获取分片文件的对象名
     *
     * @param originName
     * @param md5
     * @return
     */
    public String getChunkObjectName(String originName, String md5) {
        String fileName = md5 + originName.substring(originName.lastIndexOf("."));
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        return sdf.format(new Date()) + "/" + fileName;
    }

    /**
     * 单文件上传
     *
     * @param file 上传文件对象
     * @return Boolean
     */
    public String uploadSingle(String bucketName, MultipartFile file) throws Exception {
        String originalFilename = file.getOriginalFilename();
        // 获取存储文件名
        String objectName = getSingleObjectName(originalFilename);
        if (!bucketExists(bucketName)) {
            // 创建存储bucket
            makeBucket(bucketName);
        }
        PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(bucketName).object(objectName)
                .stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build();
        // 文件名称相同会覆盖
        minioClient.putObject(objectArgs);
        return objectName;
    }

    /**
     * 将已存在的文件移动到公共链接文件的存储目录下
     * @param sourceBucketName  源桶名称
     * @param sourceObjectName  源对象名称
     * @param fileName          文件名称
     * @return                  对象存储路径
     */
    public String moveLinkFilePath(String sourceBucketName, String sourceObjectName, String fileName) throws Exception {
        if (!bucketExists(LINK_FILE_PATH)) {
            // 创建公共链接文件存储bucket
            makeBucket(LINK_FILE_PATH);
        }
        CopySource source = CopySource.builder().bucket(sourceBucketName).object(sourceObjectName).build();
        String destObjectName = getSingleObjectName(fileName);
        CopyObjectArgs copyArgs = CopyObjectArgs.builder().source(source).bucket(LINK_FILE_PATH).object(destObjectName).build();
        minioClient.copyObject(copyArgs);
        // 删除源文件
        minioClient.removeObject(RemoveObjectArgs.builder().bucket(sourceBucketName).object(sourceObjectName).build());
        return destObjectName;
    }

    /**
     * 预览图片
     *
     * @param fileName
     * @return
     */
    public String preview(String bucketName, String fileName) {
        // 查看文件地址
        GetPresignedObjectUrlArgs build = new GetPresignedObjectUrlArgs().builder().bucket(bucketName).object(fileName).method(Method.GET).build();
        try {
            String url = minioClient.getPresignedObjectUrl(build);
            return url;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 文件下载
     *
     * @param fileName 文件名称
     * @param res      response
     * @return Boolean
     */
    public void download(String fileName, HttpServletResponse res) {
        GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(prop.getBucketName())
                .object(fileName).build();
        try (GetObjectResponse response = minioClient.getObject(objectArgs)) {
            byte[] buf = new byte[1024];
            int len;
            try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()) {
                while ((len = response.read(buf)) != -1) {
                    os.write(buf, 0, len);
                }
                os.flush();
                byte[] bytes = os.toByteArray();
                res.setCharacterEncoding("utf-8");
                // 设置强制下载不打开
                // res.setContentType("application/force-download");
                res.addHeader("Content-Disposition", "attachment;fileName=" + fileName);
                try (ServletOutputStream stream = res.getOutputStream()) {
                    stream.write(bytes);
                    stream.flush();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 查看文件对象
     *
     * @return 存储bucket内文件对象信息
     */
    public List<Item> listObjects() {
        Iterable<Result<Item>> results = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(prop.getBucketName()).build());
        List<Item> items = new ArrayList<>();
        try {
            for (Result<Item> result : results) {
                items.add(result.get());
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return items;
    }

    /**
     * 删除
     *
     * @param fileName
     * @return
     * @throws Exception
     */
    public boolean remove(String bucketName, String fileName) {
        try {
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build());
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    public Map<String, String> signPostPolicy(String bucketName, String objectName, ZonedDateTime expireTime) throws Exception {
        PostPolicy policy = new PostPolicy(bucketName, expireTime);
        policy.addEqualsCondition("key", objectName);
        return minioClient.getPresignedPostFormData(policy);
    }

    /**
     * 分片上传文件
     *
     * @param bucketName 同名称
     * @param objectName 对象名称
     * @param chunk      分片索引
     * @param file       分片文件对象
     * @return
     * @throws Exception
     */
    public boolean uploadChunk(String bucketName, String objectName, int chunk, MultipartFile file) throws Exception {
        // 上传分片文件
        ObjectWriteResponse response = minioClient.putObject(
                PutObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName + ".part" + chunk)
                        .contentType(file.getContentType())
                        .stream(file.getInputStream(), file.getSize(), -1)
                        .build()
        );
        String etag = response.etag();
        if (StrUtil.isNotEmpty(etag)) {
            return true;
        }
        return false;
    }

    /**
     * 文件合并
     *
     * @param bucketName 桶名称
     * @param objectName 对象名称
     * @param saveName   保存的文件名
     * @param chunks     分片总数
     * @throws IOException
     */
    public boolean mergeFiles(String bucketName, String objectName, String saveName, int chunks) throws IOException {
        try {
            List<ComposeSource> sources = new ArrayList<>();
            for (int i = 0; i < chunks; i++) {
                sources.add(ComposeSource.builder().bucket(bucketName).object(objectName + ".part" + i).build());
            }

            // 合并分片文件
            minioClient.composeObject(
                    ComposeObjectArgs.builder()
                            .bucket(bucketName)
                            .object(saveName)
                            .sources(sources)
                            .build()
            );

            // 删除分片文件
            for (int i = 0; i < chunks; i++) {
                minioClient.removeObject(
                        RemoveObjectArgs.builder()
                                .bucket(bucketName)
                                .object(objectName + ".part" + i)
                                .build()
                );
            }
            return true;
        } catch (Exception e) {
            log.error("文件合并失败:{}", e.getMessage(), e);
            throw new IOException("Failed to merge chunk files");
        }
    }

}
